/*
 * FS.cpp
 *
 *  Created on: 9 May 2011
 *      Author: Marc Kampherbeek
 */

#include "FS.h"
#include <iostream>
#include <fstream>
#include <bitset>
#include "Directory.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "FreeBlock.h"
#include <pwd.h>

FS::FS(char* dev) {
	username = "root"; //todo: change this to your own user id on linux
	determineUser();
	//cout << "user/group: " << getUserID() << "/" << getGroupID() << endl;
	AIT = new map<int, Inode*> ();
	OFT = new map<int, Datafile*> ();
	instance = this;
	device = dev;
	//fetch superblock
	SBlock = new SuperBlock(SUPER_LOC, readBlock(SUPER_LOC));
	//SBlock->print();
	//SBlock->getFreeBlock()->printTree();
	Inode* inode = new Inode(new InodeBlock(2, readBlock(2)), 2);
	AIT->insert(pair<int, Inode*> (2, inode));
	//AIT->at(0)->print();
}

FS* FS::instance = 0;
FS* FS::getInstance() {
	if (FS::instance == 0) {
		FS::instance = new FS("floppie");
	}
	return FS::instance;
}

void FS::determineUser() {
	struct passwd *pwd = (passwd*) calloc(1, sizeof(struct passwd));
	if (pwd == NULL) {
		fprintf(stderr, "Failed to allocate struct passwd for getpwnam_r.\n");
		exit(1);
	}
	size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
	char *buffer = (char*) malloc(buffer_len);
	if (buffer == NULL) {
		fprintf(stderr, "Failed to allocate buffer for getpwnam_r.\n");
		exit(2);
	}
	getpwnam_r(username, pwd, buffer, buffer_len, &pwd);
	if (pwd == NULL) {
		fprintf(stderr, "getpwnam_r failed to find requested entry.\n");
		exit(3);
	}
	userID = pwd->pw_uid;
	groupID = pwd->pw_gid;
}

byte* FS::readBlock(int blocknr) {
	char *data = new char[DSKBLKSIZ];
	ifstream disk(device, ios::in | ios::binary);
	if (!disk.is_open()) {
		cout << "file error" << endl;
	}
	disk.seekg(blocknr * DSKBLKSIZ);
	disk.read(data, DSKBLKSIZ);
	return (byte*) data;
}

DataBlock* FS::getDataBlock(int nr) {
	DataBlock* d = new DataBlock(nr, readBlock(nr));
	return d;
}

int FS::open(string path, int flags, mode_t mode) {
	//todo: check if inode permission flags agree with mode
	int flagCopy = flags;
	if (flagCopy>= O_TRUNC) {

		flagCopy -= O_TRUNC;
	}
	if (flagCopy>= O_EXCL) {

		flagCopy-= O_EXCL;
	}
	if (flagCopy>= O_CREAT) {
		int iCreat = creat(path.c_str(),mode);
		close(iCreat);
		flags -= O_CREAT;
	}
	//get inode
	Inode* ino;
	map<int, Inode*>::iterator it;
	int inum = getInodeNumber(path);

	cout << "FS: 1.5" << endl;
	it = AIT->find(inum);
	if(it != AIT->end()){
		ino = it->second;
		ino->setActivity(ino->getActivity()+1);
	}else{
		AIT->insert(pair<int, Inode*>(inum, findInode(path)));
		it = AIT->find(inum);
		ino = it->second;
		ino->setActivity(1);
	}
	//find number to return
	int freenumber = -1;
	for (int i = 0; i < 32; i++) {
		if (OFT->find(i) == OFT->end()) {
			cout << "NOT IN OFT" << endl;
			freenumber = i;
			break;
		}
	}
	//get datafile
	//add datafile
	if(freenumber > -1){
		cout << "FS: 6" << endl;
		OFT->insert(pair<int, Datafile*> (freenumber, new Datafile(ino, flags)));
	}
	return freenumber;
}

int FS::close(int filedes) {
	map<int, Datafile*>::iterator it;
	it = OFT->find(filedes);
	if (it == OFT->end()) {
		return -1; //not found, invalid filedescriptor
	}
	int inum = it->second->getInodeNr();
	cout << "FS: " << inum << endl;
	OFT->erase(it);
	map<int, Inode*>::iterator it2;
	it2 = AIT->find(inum);
	cout << (it2 == AIT->end());
	cout << "FS: ALIVE" << endl;
	if (it2->second->getActivity() > 1) {
		cout << "FS: A" << endl;
		it2->second->setActivity(it2->second->getActivity() - 1);
	} else {
		cout << "FS: B" << endl;
		AIT->erase(it2);
	}
	return 0;
}

ssize_t FS::read(int filedes, void *buffer, size_t n) {
	map<int, Datafile*>::iterator it;
	it = OFT->find(filedes);
	if (it == OFT->end()) {
		return -1; //not found
	}
	return it->second->read(buffer, n);
}

ssize_t FS::write(int filedes, void *buffer, size_t n) {
	map<int, Datafile*>::iterator it;
	it = OFT->find(filedes);
	if (it == OFT->end()) {
		return -1; //not found
	}
	return it->second->write(buffer, n);
}

off_t FS::lseek(int filedes, off_t offset, int start_flag) {
	map<int, Datafile*>::iterator it;
	it = OFT->find(filedes);
	if (it == OFT->end()) {
		return -1; //not found, invalid filedescriptor
	}
	return it->second->lseek(offset, start_flag);
}

int FS::creat(const char* pathname, mode_t mode) {
	cout << "punt 1" << endl;
	Inode* ino = SBlock->getFreeInode();
	if (ino == 0) {
		return -1;
	}
	cout << "punt 2" << endl;
	ino->reset();
	cout << "punt 3" << endl;

	cout << "punt 4" << endl;
	string path = pathname;
	string filename = path.substr(path.rfind("/") + 1);
	cout << path.rfind("/") << endl;
	string dirpath = path.substr(0, path.rfind("/"));
	cout << "path: " << path << endl;
	cout << "filename: " << filename << endl;
	cout << "dirpath: " << dirpath << endl;
	cout << "punt 5" << endl;
	Inode* dirino = findInode(dirpath);
	cout << dirino->getInodeNr() << endl;
	cout << "punt 6" << endl;
	int x = open(dirpath, O_RDWR);
	cout << "punt 61 : " << x << endl;
	map<int, Datafile*>::iterator it;
	cout << "punt 62" << endl;
	it = OFT->find(x);
	cout << "punt 63" << endl;
	Directory* dir = new Directory(it->second->getInode());
	cout << "punt 64" << endl;
	cout << "inum " << ino->getInodeNr() << endl;
	if (dir->addNewFile((char*) filename.c_str(), ino->getInodeNr())) {
		ino->setMode(mode);
		close(x);
		return open(pathname, O_WRONLY);
	}
	cout << "punt 7" << endl;
	close(x);
	return -1;//file already exists
}

int FS::unlink(const char* path) {
	//get inode
	int inum = getInodeNumber(path);
	cout << "inum: " << inum << endl;
	//check AIT for inode (file is open)
	//if found, refuse
	if(AIT->find(inum) != AIT->end() || inum == -1){
		cout << "is opened" << endl;
		return -1;
	}
	//check for directory(if not error)
	//if not, refuse
	Inode* ino = findInode(path);
	cout << "LINKS: " << ino->getLinks() << endl;
	if(ino->modeString().substr(0,1).compare("d") != 0){
		cout << "is not a directory" << endl;
		return -1;
	}
	//reduce link count
	ino->decreaseLinkCount(); //de link vanuit de parent gaat weg
	cout << "CHECKPOINT 1" << endl;
	//check link count for 0
	//if 0, remove

	//TODO: link count: == aantal directoris in de unlinked directory + 1 (link naar zelf)
	int minlinks = 0;
	Directory* d = new Directory(ino);
	if(d->getInodeNumber(".") == ino->getInodeNr()){
		minlinks = 1;
	}
	list<string>* filenames = d->getFilenames();
	list<string>::iterator it;
	cout << "start for loop" << endl;
	for(it = filenames->begin(); it != filenames->end(); it++){
		if((*it).compare(".") != 0 && (*it).compare("..") != 0){
			Inode* inod = findInode((*it));

			if(inod->modeString().substr(0,1).compare("d") == 0){
				cout << "found a directory: " << (*it) << endl;
				cout << inod->modeString() << endl;
				cout << inod->getMode() << endl;
				Directory* di = new Directory(inod);
				if(di->getInodeNumber("..") == ino->getInodeNr()){
					minlinks++;
				}
			}
		}
	}
	cout << "minlinks: " << minlinks << endl;
	cout << "inode: " << ino->getLinks() << endl;
	if(ino->getLinks() == minlinks){
		cout << "no more links, removing..." << endl;
		return remove(path);
	}
	//remove entry from parent directory
	string spath = (string)path;
	string pdir = spath.substr(0, spath.rfind("/"));
	Directory* dir = new Directory(findInode(pdir));
	dir->deleteFilename((char*)(spath.substr(spath.rfind("/")+1, spath.length()).c_str()));

	//
	dir = new Directory(findInode(pdir));
	dir->print();
	//

	return 0;
}

int FS::remove(const char* path) {
	//todo
	//get inode
	int inum = getInodeNumber(path);
	cout << "inum: " << inum << endl;;
	//check AIT for inode (file is open)
	//if found, refuse
	if(AIT->find(inum) != AIT->end() || inum == -1){
		cout << "file is opened" << endl;
		return -1;
	}
	//check for directory
	//if directory, and if link count > 0
	//return unlink
	//else
	//do unlink for every directory inside the unlinked directory
	//do remove for every file inside the unlinked directory
	Inode* ino = findInode(path);
	cout << ino->modeString() << endl;
	if(ino->modeString().substr(0,1).compare("d") == 0){
		cout << "its a directory, doing unlink instead" << endl;
		if(ino->getLinks() > 0){
			return unlink(path);
		}else{
			Directory* dir = new Directory(ino);
			list<string>* filenames = dir->getFilenames();
			list<string>::iterator it;
			for(it = filenames->begin(); it != filenames->end(); it++){
				if((*it).compare(".") != 0 && (*it).compare("..") != 0){
					string fpath = path;
					fpath.append("/");
					fpath.append((*it));
					remove(fpath.c_str());
				}
			}
		}
	}
	//free blocks
	cout << "freeing inode" << endl;
	ino->free();
	//reset inode
	cout << "resetting inode" << endl;
	ino->reset();
	//remove entry from parent directory
	string spath = (string)path;
	string pdir = spath.substr(0, spath.rfind("/"));
	cout << "path parent: " << pdir << endl;
	Directory* dir = new Directory(findInode(pdir));
	dir->deleteFilename((char*)(spath.substr(spath.rfind("/")+1, spath.length()).c_str()));
	cout << "alive?" << endl;

	//
	dir = new Directory(findInode(pdir));
	dir->print();
	//

	return 0;
}

void FS::addFreeBlock(long addr){
	SBlock->getFreeBlock()->add(addr);
}

void FS::writeBlock(int blocknr, byte* buffer, size_t count){
	ofstream disk(device, ios::in | ios::binary);
	disk.seekp(blocknr * DSKBLKSIZ);
	disk.write((char*) buffer, count);
}

void FS::writeInBlock(int blocknr, int pos, byte* buffer, size_t count) {
	ofstream disk(device, ios::in | ios::binary);
	disk.seekp(blocknr * DSKBLKSIZ + pos);
	disk.write((char*) buffer, count);
}

ssize_t FS::write(Inode* ino, int iopos, void* buffer, size_t count) {
	//cout << "START OF WRITE" << endl;
	int done = 0; //keep track of the amount of data written to the buffer
	int startBlock = iopos / DSKBLKSIZ; //startblock within the inode
	int startpos = iopos; //keep track of the value of iopos when this method started
	//cout << "startpos: " << startpos << endl;
	//start might be at the last byte of one block, so we have to calculate iopos along with it
	ofstream disk(device, ios::in | ios::binary);
	for (int i = 0; i <= ((startpos % DSKBLKSIZ) + count) / DSKBLKSIZ; i++) { //in case it covers multiple blocks
		int currentBlock = startBlock + i;
		int writeToBlock; //amount we need to write to current block
		if (i == ((startpos % DSKBLKSIZ) + count) / DSKBLKSIZ) { //last block to read from?
			if (i == 0) {
				//cout << "determined at point a" << endl;
				writeToBlock = DSKBLKSIZ - (startpos % DSKBLKSIZ);
				if (count < writeToBlock) {
					//cout << "determined at point b" << endl;
					writeToBlock = count;
				}
			} else {
				//cout << "determined at point c" << endl;
				writeToBlock = ((startpos % DSKBLKSIZ) + count) % DSKBLKSIZ;
			}
		} else {
			if (i == 0) {
				//cout << "determined at point d" << endl;
				writeToBlock = DSKBLKSIZ - (startpos % DSKBLKSIZ);
			} else {
				//cout << "determined at point e" << endl;
				writeToBlock = DSKBLKSIZ;
			}
		}
		//cout << "need to write " << writeToBlock << " bytes" << endl;
		//determine block number on disk
		int diskBlock = ino->getDiskBlockNr(currentBlock);
		//cout << "diskblock: " << diskBlock << endl;
		if (diskBlock == 0) {//nonexistant block
			//determine new block nr
			diskBlock = SBlock->getFreeBlockAddress();
			//add block address to inode
			ino->setAddr(currentBlock, diskBlock);
			//if the new block is added at the end of the file: determine new file size
			if (iopos + count > ino->getSize()) {
				ino->setSize(iopos + count);
			}
		}
		//generate subarray to write to this specific block
		byte data[writeToBlock + 1];
		for (int j = 0; j < writeToBlock; j++) {
			data[j] = *((byte*) buffer + done);
			//cout << writeToBlock << endl;
			//cout << *((char*)buffer + done) << endl;
			//cout << data << endl;
			done++;
		}
		data[writeToBlock] = '\0';
		//set startposition on device
		int writeloc = 0;
		if (i == 0) {
			writeloc = ((diskBlock * DSKBLKSIZ) + startpos);
		} else {
			writeloc = ((diskBlock * DSKBLKSIZ));
		}
		//cout << "writeloc: " << writeloc << endl;
		disk.seekp(writeloc);
		//write to device
		//cout << "writing: " << data << endl;
		//cout << "location: " << writeloc << endl;
		disk.write((char*) data, (long) writeToBlock);

	}
	//
	//cout << "END OF WRITE" << endl;
	return done;
}

int FS::getInodeNumber(string path) {
	cout << "FSGI: 1" << endl;
	if (path.size() == 0 || path.compare("/") == 0) {
		return 2;
	}
	char * cstr, *p;
	cstr = new char[path.size() + 1];
	strcpy(cstr, path.c_str());
	// cstr now contains a c-string copy of path
	p = strtok(cstr, "/");
	vector<char*>* pathtree = new vector<char*> ();
	while (p != NULL) {
		pathtree->push_back(p);
		p = strtok(NULL, "/");
	}

	Directory* d = new Directory(findInode("/"));
	//Directory* d = root;
	int inum;
	for (unsigned int i = 0; i < pathtree->size(); i++) {
		//pathtree->a
		inum = d->getInodeNumber(pathtree->at(i));
		int iblocknr = ((inum - 1) / 8) + 2;
		if (i != pathtree->size() - 1) {
			Inode* inode = new Inode(
					new InodeBlock(iblocknr, readBlock(iblocknr)), inum);
			d = new Directory(inode);
		} else {
			return inum;
		}
	}
	return -1;
}

Inode* FS::findInode(string path) {
	int inum = getInodeNumber(path);
	int iblocknr = ((inum - 1) / 8) + 2;
	return new Inode(new InodeBlock(iblocknr, readBlock(iblocknr)), inum);
}

FS::~FS() {
	delete device;
	delete SBlock;
	delete instance;
	delete[] cache;
}
